home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / pcl / src-16f.lha / code / dyncount.lisp < prev    next >
Lisp/Scheme  |  1992-05-30  |  14KB  |  432 lines

  1. ;;; -*- Package: C; Log: C.Log -*-
  2. ;;;
  3. ;;; **********************************************************************
  4. ;;; This code was written as part of the CMU Common Lisp project at
  5. ;;; Carnegie Mellon University, and has been placed in the public domain.
  6. ;;; If you want to use this code or any part of CMU Common Lisp, please contact
  7. ;;; Scott Fahlman or slisp-group@cs.cmu.edu.
  8. ;;;
  9. (ext:file-comment
  10.   "$Header: dyncount.lisp,v 1.2 92/02/13 09:54:29 ram Exp $")
  11. ;;;
  12. ;;; **********************************************************************
  13. ;;;
  14. ;;; Runtime support for dynamic VOP statistics collection.
  15. ;;; 
  16. (in-package "C")
  17.  
  18. #|
  19. Put *count-adjustments* back into VOP costs, and verify them.
  20. Make sure multi-cycle instruction costs are plausible.
  21. VOP classification.
  22.   Make tables of %cost for benchmark X class.
  23.   Could be represented as a sort of bar chart.
  24. |#
  25.  
  26. (eval-when (compile)
  27.   (when *collect-dynamic-statistics*
  28.     (error "Compiling this file with dynamic stat collection turn on would ~
  29.     be a very bad idea.")))
  30.  
  31. ;;;; Hash utilities:
  32.  
  33. (defmacro do-hash ((key-var value-var table &optional result)
  34.            &body (body decls))
  35.   "DO-HASH (Key-Var Value-Var Table [Result]) Declaration* Form*
  36.    Iterate over the entries in a hash-table."
  37.   (let ((gen (gensym))
  38.     (n-more (gensym)))
  39.     `(with-hash-table-iterator (,gen ,table)
  40.        (loop
  41.      (multiple-value-bind (,n-more ,key-var ,value-var)
  42.                   (,gen)
  43.        ,@decls
  44.        (unless ,n-more (return ,result))
  45.        ,@body)))))
  46.  
  47. (defun make-hash-table-like (table)
  48.   "Make a hash-table with the same test as table."
  49.   (declare (type hash-table table))
  50.   (make-hash-table :test (lisp::hash-table-kind table)))
  51.  
  52. (defun hash-difference (table1 table2)
  53.   "Return a hash-table containing only the entries in Table1 whose key is not
  54.    also a key in Table2." (declare (type hash-table table1 table2))
  55.   (let ((res (make-hash-table-like table1)))
  56.     (do-hash (k v table1)
  57.       (unless (nth-value 1 (gethash k table2))
  58.     (setf (gethash res k) v)))
  59.     res))
  60.  
  61. (defun hash-list (table)
  62.   "Return a list of the values in Table."
  63.   (declare (type hash-table table))
  64.   (collect ((res))
  65.     (do-hash (k v table)
  66.       (declare (ignore k))
  67.       (res v))
  68.     (res)))
  69.  
  70. ;;; READ-HASH-TABLE, WRITE-HASH-TABLE  --  Public
  71. ;;;
  72. ;;;    Read (or write) a hashtable from (or to) a file.
  73. ;;;
  74. (defun read-hash-table (file)
  75.   (with-open-file (s file :direction :input)
  76.     (dotimes (i 3)
  77.       (format t "~%; ~A" (read-line s)))
  78.     (let* ((eof '(nil))
  79.        (test (read s))
  80.        (reader (read s))
  81.        (res (make-hash-table :test test)))
  82.       (read s); Discard writer...
  83.       (loop
  84.     (let ((key (read s nil eof)))
  85.       (when (eq key eof) (return))
  86.       (setf (gethash key res)
  87.         (funcall reader s key))))
  88.       res)))
  89. ;;;
  90. (defun write-hash-table (table file &key
  91.                    (comment (format nil "Contents of ~S" table))
  92.                    (reader 'read) (writer 'prin1) (test 'equal))
  93.   (with-open-file (s file :direction :output :if-exists :new-version)
  94.     (with-standard-io-syntax
  95.       (let ((*print-readably* nil))
  96.     (format s "~A~%Version ~A on ~A~%"
  97.         comment (lisp-implementation-version)
  98.         (machine-instance))
  99.     (format-universal-time s (get-universal-time))
  100.     (terpri s)
  101.     (format s "~S ~S ~S~%" test reader writer)
  102.     (do-hash (k v table)
  103.       (prin1 k s)
  104.       (write-char #\space s)
  105.       (funcall writer v s)
  106.       (terpri s)))))
  107.   table)
  108.  
  109.  
  110. ;;;; Info accumulation:
  111.  
  112. ;;; Used to accumulate info about the usage of a single VOP.  Cost and count
  113. ;;; are kept as double-floats, which lets us get more bits and avoid annoying
  114. ;;; overflows.
  115. ;;;
  116. (deftype count-vector () '(simple-array double-float (2)))
  117. ;;;
  118. (defstruct (vop-stats
  119.         (:constructor %make-vop-stats (name))
  120.         (:constructor make-vop-stats-key))
  121.   (name (required-argument) :type simple-string)
  122.   (data (make-array 2 :element-type 'double-float) :type count-vector))
  123.  
  124. (defmacro vop-stats-count (x) `(aref (vop-stats-data ,x) 0))
  125. (defmacro vop-stats-cost (x) `(aref (vop-stats-data ,x) 1))
  126.  
  127. (defun make-vop-stats (&key name count cost)
  128.   (let ((res (%make-vop-stats name)))
  129.     (setf (vop-stats-count res) count)
  130.     (setf (vop-stats-cost res) cost)
  131.     res))
  132.     
  133. (declaim (freeze-type dyncount-info vop-stats))
  134.  
  135.  
  136. ;;; NOTE-DYNCOUNT-INFO  --  Internal
  137. ;;;
  138. ;;;    Add the Info into the cumulative result on the VOP name plist.  We use
  139. ;;; plists so that we will touch minimal system code outside of this file
  140. ;;; (which may be compiled with profiling on.)
  141. ;;;
  142. (defun note-dyncount-info (info)
  143.   (declare (type dyncount-info info) (inline get %put)
  144.        (optimize (speed 2)))
  145.   (let ((counts (dyncount-info-counts info))
  146.     (vops (dyncount-info-vops info)))
  147.     (dotimes (index (length counts))
  148.       (declare (type index index))
  149.       (let ((count (coerce (the (unsigned-byte 31)
  150.                 (aref counts index))
  151.                'double-float)))
  152.     (when (minusp count)
  153.       (warn "Oops: overflow.")
  154.       (return-from note-dyncount-info nil))
  155.     (unless (zerop count)
  156.       (let* ((vop-info (svref vops index))
  157.          (length (length vop-info)))
  158.         (declare (simple-vector vop-info))
  159.         (do ((i 0 (+ i 4)))
  160.         ((>= i length))
  161.           (declare (type index i))
  162.           (let* ((name (svref vop-info i))
  163.              (entry (or (get name 'vop-stats)
  164.                 (setf (get name 'vop-stats)
  165.                       (%make-vop-stats (symbol-name name))))))
  166.         (incf (vop-stats-count entry)
  167.               (* (coerce (the index (svref vop-info (1+ i)))
  168.                  'double-float)
  169.              count))
  170.         (incf (vop-stats-cost entry)
  171.               (* (coerce (the index (svref vop-info (+ i 2)))
  172.                  'double-float)
  173.              count))))))))))
  174.  
  175. (defun clear-dyncount-info (info)
  176.   (declare (type dyncount-info info))
  177.   (declare (optimize (speed 3) (safety 0)))
  178.   (let ((counts (dyncount-info-counts info)))
  179.     (dotimes (i (length counts))
  180.       (setf (aref counts i) 0))))
  181.  
  182.  
  183. ;;; CLEAR-VOP-COUNTS  --  Public
  184. ;;;
  185. ;;;    Clear any VOP-COUNTS properties and the counts vectors for all code
  186. ;;; objects.  The latter loop must not call any random functions.
  187. ;;;
  188. (defun clear-vop-counts (&optional (spaces '(:dynamic)))
  189.   "Clear all dynamic VOP counts for code objects in the specified spaces."
  190.   (do-hash (k v (backend-template-names *backend*))
  191.     (declare (ignore v))
  192.     (remprop k 'vop-stats))
  193.   
  194.   (locally
  195.       (declare (optimize (speed 3) (safety 0))
  196.            (inline vm::map-allocated-objects))
  197.     (without-gcing
  198.       (dolist (space spaces)
  199.     (vm::map-allocated-objects
  200.      #'(lambda (object type-code size)
  201.          (declare (ignore type-code size))
  202.          (when (dyncount-info-p object)
  203.            (clear-dyncount-info object)))
  204.      space)))))
  205.  
  206.  
  207. ;;; GET-VOP-COUNTS  --  Public
  208. ;;;
  209. ;;;    Call NOTE-DYNCOUNT-INFO on all DYNCOUNT-INFO structure allocated in the
  210. ;;; specified spaces.  Return a hashtable describing the counts.  The initial
  211. ;;; loop must avoid calling any functions outside this file to prevent adding
  212. ;;; noise to the data, since other files may be compiled with profiling.
  213. ;;;
  214. (defun get-vop-counts (&optional (spaces '(:dynamic)) &key (clear t))
  215.   "Return a hash-table mapping string VOP names to VOP-STATS structures
  216.    describing the VOPs executed.  If clear is true, then reset all counts to
  217.    zero as a side-effect."
  218.   (locally
  219.       (declare (optimize (speed 3) (safety 0))
  220.            (inline vm::map-allocated-objects))
  221.     (without-gcing
  222.       (dolist (space spaces)
  223.     (vm::map-allocated-objects
  224.      #'(lambda (object type-code size)
  225.          (declare (ignore type-code size))
  226.          (when (dyncount-info-p object)
  227.            (note-dyncount-info object)
  228.            (when clear
  229.          (clear-dyncount-info object))))
  230.      space))))
  231.   
  232.   (let ((counts (make-hash-table :test #'equal)))
  233.     (do-hash (k v (backend-template-names *backend*))
  234.       (declare (ignore v))
  235.       (let ((stats (get k 'vop-stats)))
  236.     (when stats
  237.       (setf (gethash k counts) stats)
  238.       (when clear
  239.         (remprop k 'vop-stats)))))
  240.     counts))
  241.  
  242.  
  243. ;;; FIND-INFO-FOR  --  Interface
  244. ;;;
  245. ;;;    Return the DYNCOUNT-INFO for FUNCTION.
  246. ;;;
  247. (defun find-info-for (function)
  248.   (declare (type function function))
  249.   (let* ((function (%primitive closure-function function))
  250.      (component (di::function-code-header function)))
  251.     (do ((end (get-header-data component))
  252.      (i vm:code-constants-offset (1+ i)))
  253.     ((= end i))
  254.       (let ((constant (code-header-ref component i)))
  255.     (when (dyncount-info-p constant)
  256.       (return constant))))))
  257.  
  258.  
  259. (defun vop-counts-apply (function args &key (spaces '(:dynamic)) by-space)
  260.   "Apply Function to Args, collecting dynamic statistics on the running.
  261.    Spaces are the spaces to scan for counts.  If By-Space is true, we return a
  262.    list of result tables, instead of a single table.  In this case, specify
  263.    :READ-ONLY first."
  264.   (clear-vop-counts spaces)
  265.   (apply function args)
  266.   (if by-space
  267.       (mapcar #'get-vop-counts spaces)
  268.       (get-vop-counts spaces)))
  269.  
  270. ;;;; Adjustments:
  271.  
  272. (defparameter *count-adjustments*
  273.   '((return-multiple 152)
  274.     (tail-call-variable 88)
  275.     (unwind 92)
  276.     (throw 116)
  277.     (allocate-vector 72)
  278.     (sxhash-simple-string 248)
  279.     (sxhash-simple-substring 264)
  280.     (copy-to-system-area 1200)
  281.     (copy-from-system-area 1200)
  282.     (system-area-copy 1204)
  283.     (bit-bash-copy 1412)
  284.     (vm::generic-+ 72)
  285.     (vm::generic-- 72)
  286.     (vm::generic-* 184)
  287.     (vm::generic-< 68)
  288.     (vm::generic-> 68)
  289.     (vm::generic-eql 80)
  290.     (vm::generic-= 80)
  291.     (vm::generic-/= 104)
  292.     (%make-weak-pointer 60)
  293.     (make-value-cell 56)
  294.     (vm::make-funcallable-instance 76)
  295.     (make-closure 76)
  296.     (make-complex 60)
  297.     (make-ratio 60)
  298.     (%allocate-bignum 72)
  299.     (make-structure 72)
  300.     (cons 50)))
  301.  
  302. ;;; GET-VOP-COSTS  --  Public
  303. ;;;
  304. (defun get-vop-costs ()
  305.   "Return a hash-table mapping string VOP names to the cost recorded in the
  306.    generator for all VOPs which are also the names of assembly routines."
  307.   (let ((res (make-hash-table :test #'equal)))
  308.      (do-hash (name v lisp::*assembler-routines*)
  309.        (declare (ignore v))
  310.        (let ((vop (gethash name (backend-template-names *backend*))))
  311.      (when vop
  312.        (setf (gethash (symbol-name name) res)
  313.          (template-cost (template-or-lose name))))))
  314.     res))
  315.  
  316. (defvar *native-costs* (get-vop-costs)
  317.   "Costs of assember routines on this machine.")
  318.  
  319.  
  320. ;;;; Analysis and report generation:
  321.  
  322. ;;; COST-SUMMARY  --  Internal
  323. ;;;
  324. ;;;    Sum the count and costs.
  325. ;;;
  326. (defun cost-summary (table)
  327.   (let ((total-count 0d0)
  328.     (total-cost 0d0))
  329.     (do-hash (k v table)
  330.       (declare (ignore k))
  331.       (incf total-count (vop-stats-count v))
  332.       (incf total-cost (vop-stats-cost v)))
  333.     (values total-count total-cost)))
  334.  
  335.   
  336. ;;; COMPENSATE-COSTS  --  Internal
  337. ;;;
  338. ;;;    Return a hashtable of DYNCOUNT-INFO structures, with cost adjustments
  339. ;;; according to the Costs table.
  340. ;;;
  341. (defun compensate-costs (table costs)
  342.   (let ((res (make-hash-table-like table)))
  343.     (do-hash (key value table)
  344.       (unless (string= key "COUNT-ME")
  345.     (let ((cost (gethash key costs)))
  346.       (if cost
  347.           (let* ((count (vop-stats-count value))
  348.              (sum (+ (* cost count)
  349.                  (vop-stats-cost value))))
  350.         (setf (gethash key res)
  351.               (make-vop-stats :name key :count count :cost sum)))
  352.           (setf (gethash key res) value)))))
  353.     res))
  354.  
  355.  
  356. ;;; COMBINE-STATS  --  Internal
  357. ;;;
  358. ;;;    Take two tables of vop-stats and return a table of entries where the
  359. ;;; entries have been compared somehow.  The counts are normalized to Compared.
  360. ;;; The costs are the difference of the costs adjusted by the difference in
  361. ;;; counts: the cost for Original is modified to correspond to the count in
  362. ;;; Compared.
  363. ;;;
  364. (defun combine-stats (original compared)
  365.   (declare (type hash-table original compared))
  366.   (let ((res (make-hash-table-like original)))
  367.     (do-hash (k cv compared)
  368.       (let ((ov (gethash k original)))
  369.     (when ov
  370.       (let ((norm-cnt (/ (vop-stats-count ov) (vop-stats-count cv))))
  371.         (setf (gethash k res)
  372.           (make-vop-stats
  373.            :name k
  374.            :count norm-cnt
  375.            :cost (- (/ (vop-stats-cost ov) norm-cnt)
  376.                 (vop-stats-cost cv))))))))
  377.     res))
  378.  
  379.  
  380. ;;; SORT-RESULT  --  Internal
  381. ;;;
  382. (defun sort-result (table by)
  383.   (sort (hash-list table) #'>
  384.     :key #'(lambda (x)
  385.          (abs (ecase by
  386.             (:count (vop-stats-count x))
  387.             (:cost (vop-stats-cost x)))))))
  388.  
  389.  
  390. ;;; GENERATE-REPORT  --  Public
  391. ;;;
  392. ;;; Generate a report from the specified table.
  393. ;;;
  394. (defun generate-report (table &key (cut-off 15) (sort-by :cost)
  395.                   (costs *native-costs*) compare)
  396.   (let* ((compensated (if costs (compensate-costs table costs) table))
  397.      (compared (if compare
  398.                (combine-stats compensated compare)
  399.                compensated))
  400.      (*gc-verbose* nil))
  401.     (multiple-value-bind (total-count total-cost)
  402.              (cost-summary (or compare compensated))
  403.       (format t "~30<Vop~>  ~13<Count~> ~6<Cost~>  ~6:@<Percent~>~%")
  404.       (dolist (entry (sort-result compared sort-by))
  405.     (when (and cut-off (minusp (decf cut-off)))
  406.       (return))
  407.     (let* ((cost (vop-stats-cost entry))
  408.            (name (vop-stats-name entry))
  409.            (entry-count (vop-stats-count entry))
  410.            (comp-entry (if compare (gethash name compare) entry))
  411.            (count (vop-stats-count comp-entry)))
  412.       (format t "~30<~A~>: ~:[~13:D~;~13,2F~] ~6,1F  ~4,1,2F%~%"
  413.           (vop-stats-name entry)
  414.           compare
  415.           (if compare entry-count (round entry-count))
  416.           (/ cost count)
  417.           (/ cost total-cost))))
  418.     (format t "~%Total count ~,3E, total cost ~,3E.~%"
  419.         total-count total-cost)))
  420.   (values))
  421.  
  422.  
  423. ;;; STATS-{READER,WRITER}  --  Public
  424. ;;;
  425. ;;;    Read & write VOP stats using hash IO utility.
  426. ;;;
  427. (defun stats-reader (stream key)
  428.   (make-vop-stats :name key :count (read stream) :cost (read stream)))
  429. ;;;
  430. (defun stats-writer (object stream)
  431.   (format stream "~S ~S" (vop-stats-count object) (vop-stats-cost object)))
  432.